package com.googlecode.compliancechecker;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

/**
 * Utility class to collect all type- and method-references from a source file.
 * 
 * @author Marc-Christian Schulze
 * 
 */
public class ReferenceCollector {

	public Set<TypeReference> analyze(InputStream stream) throws FileNotFoundException, IOException {

		Set<TypeReference> references = new HashSet<TypeReference>();
		ClassVisitor cv = new MyVisistor(references);
		ClassReader cr = new ClassReader(stream);
		cr.accept(cv, 0);

		return references;
	}

	private class MyVisistor implements ClassVisitor, MethodVisitor {

		private Set<TypeReference>	references;
		private int						currentLine;

		public MyVisistor(Set<TypeReference> references) {
			this.references = references;
		}

		public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
			references.add(new TypeReference(currentLine, superName));
			if (interfaces != null) {
				for (String ifc : interfaces) {
					references.add(new TypeReference(currentLine, ifc));
				}
			}
		}

		public void visitTypeInsn(int opcode, String type) {
			// New, cast or instanceof
			if (type == null) {
				return;
			}
			references.add(new TypeReference(currentLine, type));
		}

		public void visitFieldInsn(int opcode, String owner, String name, String desc) {
			// Field access
			SignatureTypeExtractor typeExtractor = new SignatureTypeExtractor(desc);

			List<TypeReference> typesOfSignature = typeExtractor.getTypesOfSignature();
			references.addAll(typesOfSignature);
		}

		public void visitMethodInsn(int opcode, String owner, String name, String desc) {
			// Method invocation
			references.add(new MethodReference(currentLine, owner, name, desc));
			SignatureTypeExtractor typeExtractor = new SignatureTypeExtractor(desc);

			List<TypeReference> typesOfSignature = typeExtractor.getTypesOfSignature();
			references.addAll(typesOfSignature);
		}

		public void visitMultiANewArrayInsn(String desc, int dims) {
			SignatureTypeExtractor typeExtractor = new SignatureTypeExtractor(desc);

			List<TypeReference> typesOfSignature = typeExtractor.getTypesOfSignature();
			references.addAll(typesOfSignature);
		}

		public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
			// Catched exception class
			if (type == null) {
				return;
			}
			references.add(new TypeReference(currentLine, type));
		}

		public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
			SignatureTypeExtractor typeExtractor = new SignatureTypeExtractor(desc);

			List<TypeReference> typesOfSignature = typeExtractor.getTypesOfSignature();
			references.addAll(typesOfSignature);
		}

		public void visitLineNumber(int line, Label start) {
			currentLine = line;
		}

		public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
			SignatureTypeExtractor typeExtractor = new SignatureTypeExtractor(desc);

			List<TypeReference> typesOfSignature = typeExtractor.getTypesOfSignature();
			references.addAll(typesOfSignature);
			return null;
		}

		public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
			SignatureTypeExtractor typeExtractor = new SignatureTypeExtractor(desc);

			List<TypeReference> typesOfSignature = typeExtractor.getTypesOfSignature();
			references.addAll(typesOfSignature);
			return null;
		}

		public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

			SignatureTypeExtractor typeExtractor = new SignatureTypeExtractor(desc);

			List<TypeReference> typesOfSignature = typeExtractor.getTypesOfSignature();
			references.addAll(typesOfSignature);

			if (exceptions != null) {
				for (String exc : exceptions) {
					references.add(new TypeReference(currentLine, exc));
				}
			}

			return this;
		}

		public void visitEnd() {
		}

		public AnnotationVisitor visitAnnotationDefault() {
			return null;
		}

		public void visitCode() {
		}

		public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
		}

		public void visitInsn(int opcode) {
		}

		public void visitIntInsn(int opcode, int operand) {
		}

		public void visitVarInsn(int opcode, int var) {
		}

		public void visitJumpInsn(int opcode, Label label) {
		}

		public void visitLabel(Label label) {
		}

		public void visitLdcInsn(Object cst) {
		}

		public void visitIincInsn(int var, int increment) {
		}

		public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
		}

		public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
		}

		public void visitSource(String source, String debug) {
		}

		public void visitOuterClass(String owner, String name, String desc) {
		}

		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			return null;
		}

		public void visitAttribute(Attribute attr) {
		}

		public void visitInnerClass(String name, String outerName, String innerName, int access) {
		}

		public void visitMaxs(int maxStack, int maxLocals) {
		}
	}
}
